#!/usr/bin/env python

# Copyright 2011 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import fix_path
import os

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext.webapp import template
from google.appengine.ext import db

from django.utils import simplejson as json
import cgi
import datetime
import logging
import urllib

from lib import authn
from lib import error
from lib import http
from lib import utils

import model
import app_params as _idp_params

from lib.loggable import loggable

__author__ = "Maciej Machulak"
__maintainer__ = "Maciej Machulak"
__email__ = "mmachulak@google.com"

__copyright__ = "Copyright 2011, Google Inc."
__license__ = "Apache License 2.0"
__version__ = "0.1"
__status__ = "Prototype"

class AuthorizationHandler(webapp.RequestHandler):
  
    @authn.login_required
    @loggable('authorize')
    def get(self):
      
      user = self.user
      response_type = cgi.escape(self.request.get('response_type'))
      if response_type is None or response_type != 'code':
        logging.error('incorrect response type')
        error.handle_error(self)
        return       
      client_id = cgi.escape(self.request.get('client_id'))
      redirect_uri = cgi.escape(self.request.get('redirect_uri'))
      scopes = cgi.escape(self.request.get('scope'))
      state = cgi.escape(self.request.get('state'))
      scope_list = scopes.split(' ')
          
      application = model.Application.all().filter('client_id = ', client_id).get()
      if application is None:
        logging.error('application not found')
        error.handle_error(self)
        return
      if redirect_uri != application.redirect_uri:
        logging.error('provided redirect_uri: ' + redirect_uri)
        logging.error('application redirect_uri: ' + application.redirect_uri)
        logging.error('redirect uri does not match')
        error.handle_error(self)
        return
      
      authz = None
      for authorization in user.authorization_list:
        if authorization.application.client_id == client_id:
          authz = authorization
          break
        
      if authz is not None:
        # TODO: I need to check whether scopes match those originally requested
        logging.debug('found existing authorization - automatically generating code')
        authz.code = utils.random()
        authz.code_expires_at = datetime.datetime.now() + datetime.timedelta(hours=1) 
        authz.put()
        logging.debug('new code generation for the authorization')
        redirect_location = redirect_uri + '?code='+ authz.code
        if state and len(state) > 0:
          redirect_location = redirect_location + '&state=' + state
        self.redirect(redirect_location)
        return
      logging.debug('authorization has not been found')  
      requested_scopes = []
      for scope_id in scope_list:
        scope = model.Scope.all().filter('id = ', scope_id).get()
        if scope is None:
          logging.error('scope not found: %s ' % scope_id)
          self.redirect(redirect_uri + '?error=invalid_scope')
          return
        requested_scopes.append(scope)
      
      values = {
            'user': user,
            'application': application,
            'requested_scopes': requested_scopes,
            'query': self.request.query_string,
            'url': urllib.quote_plus(self.request.url)    
      }
      path = os.path.join(os.path.dirname(__file__), '../templates/authz.html')
      self.response.out.write(template.render(path, values))

    @authn.login_required
    @loggable('authorize')    
    def post(self):
      user = self.user
      response_type = cgi.escape(self.request.get('response_type'))
      if response_type is None or response_type != 'code':
        logging.error('incorrect response type')
        error.handle_error(self)
        return   
      client_id = cgi.escape(self.request.get('client_id'))
      redirect_uri = cgi.escape(self.request.get('redirect_uri'))
      scopes = cgi.escape(self.request.get('scope'))
      scope_list = scopes.split(',')
      state = cgi.escape(self.request.get('state'))
      
      application = model.Application.all().filter('client_id = ', client_id).get()
      if application is None:
        logging.error('application not found')
        error.handle_error(self)
        return
      if redirect_uri != application.redirect_uri:
        logging.error('provided redirect_uri: ' + redirect_uri)
        logging.error('application redirect_uri: ' + application.redirect_uri)
        logging.error('redirect uri does not match')
        error.handle_error(self)
        return
      
      decision = cgi.escape(self.request.get('decision'))
      if decision != _idp_params.Template.DECISION_ALLOW:
        logging.debug('user denied access')
        self.redirect(redirect_uri + '?error=access_denied')
        return
      
      requested_scopes = []
      for scope_id in scope_list:
        scope = model.Scope.all().filter('id = ', scope_id).get()
        if scope is None:
          logging.error('scope not found: %s ' % scope_id)
          self.redirect(redirect_uri + '?error=invalid_scope')
          return
        requested_scopes.append(scope.id)
      
      authorization = model.Authorization.all().filter('user.key = ', user.key()).filter('application.key = ', application.key()).get()      
      
      if authorization is None:
        logging.debug('creating a new authorization')
        authorization = model.Authorization(
           application = application,
           user = user,
           scopes = requested_scopes,
           code = utils.random(),
           code_expires_at = datetime.datetime.now() + datetime.timedelta(hours=1)            
        )  
        authorization.put()
        logging.debug('authorization stored in db')
      else:
        logging.info('found existing authorization - generating new code')
        authorization.code = utils.random()
        authorization.code_expires_at = datetime.datetime.now() + datetime.timedelta(hours=1)  
      redirect_location = redirect_uri + '?code='+ authorization.code
      if state is not None and len(state) > 0:
        logging.debug('appending state to the redirect uri')
        redirect_location = redirect_location + '&state=' + state
      logging.debug('redirecting user to %s' % redirect_location)
      self.redirect(redirect_location)

class TokenHandler(webapp.RequestHandler):
 
    @loggable('process error')
    def __process_error(self, status, error, error_description = None):
      error_response = []
      error_response['error'] = error
      if error_description:
        error_response['error_description'] = error_description
      self.response.set_status(status)
      self.response.out.write(json.dumps(error_response))
      
    
    @loggable('token')  
    def post(self):
      
      client_id = self.request.get('client_id')
      client_secret = self.request.get('client_secret')
      code = self.request.get('code')
      
      # Check if the request is correct
      if client_id is None or client_id == '':
        self.__process_error(400, 'invalid_request', 'client id is missing')
        return

      if client_secret is None or client_secret == '':
        self.__process_error(400, 'invalid_request', 'client secret is missing')        
        return

      if code is None or code == '':
        self.__process_error(400, 'invalid_request', 'code is missing')   
        return
      
      # Obtain authorization from DB
      authorization = model.Authorization().all().filter('code = ', code).get()
      if authorization is None:
        logging.error('authorization could not be found')
        self.__process_error(400, 'invalid_grant', 'code is invalid')           
        return
      
      # Check if this authorization belongs to this client
      app = authorization.application
      if app.client_id != client_id or app.client_secret != client_secret:
        logging.error('unauthorized client for this client secret')
        self.__process_error(400, 'unauthorized_client', 'client id or client secret were incorrect')
        return
      
      # If everything is ok then issue the access token
      issued_at = datetime.datetime.now()
      expires_in = datetime.timedelta(hours=1)
      authorization.code = ''
      authorization.access_token = utils.random()
      authorization.refresh_token = utils.random()
      authorization.issued_at = issued_at
      authorization.expires_at = authorization.issued_at + expires_in
      authorization.put()
      
      token_response = {}
      token_response['access_token'] = authorization.access_token
      token_response['refresh_token'] = authorization.refresh_token
      token_response['expires_in'] = expires_in.seconds

      logging.debug('returning token response')
      logging.debug(token_response)
      self.response.set_status(200)
      self.response.headers[http.Header.pragma] = 'no-cache'
      self.response.headers[http.Header.content_type] = 'application/json'
      self.response.out.write(json.dumps(token_response))      
